"""Python缠论策略3 - 主窗口的中央窗体
本程序的界面参考了《Python量化交易从入门到实战》一书第11章示例程序buttons.py。
本程序的策略与示例1完全相同。
"""

# coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *
from threading import Thread
from typing import Callable
from PyQt5.QtWidgets import (QWidget, QPushButton, QTextEdit,
                             QHBoxLayout, QVBoxLayout, QApplication)
from PyQt5.QtCore import pyqtSignal

from manager import BarManager

""" 以下为策略代码 """
def init(context):
    context.symbol = 'CFFEX.IC2301'  # 交易标的

    # 创建一个K线管理器
    context.manager: BarManager = BarManager()

    context.position = 0  # 仓位
    context.cancel_price = 0.0  # 止损价

    subscribe(context.symbol, '300s', count=1)  # 订阅行情


def on_bar(context, bars):
    # 对接收到的每根K线进行操作
    for bar in bars:
        # 更新数据并进行缠论计算
        context.manager.update_bar(bar)
        if context.manager.errNo.value != 0:
            # 如果缠论计算失败，不继续操作
            # 注：缠论计算需要一定数据量，所以回测开始的一段时间不会有操作
            continue

        # 如果当前空仓
        if context.position == 0:
            # 取缠论开仓信息
            ret, cancel_price = context.manager.is_buy_point()
            if ret != 0:
                # 如果有买点
                context.log_func('{}: 标的：{}，买卖点：{}'.format(context.now, context.symbol, ret))

            if ret < 0:
                # 开多仓
                order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy, position_effect=PositionEffect_Open,
                             order_type=OrderType_Market)
                context.log_func('{}:标的：{}，操作：开多仓，委托价格：{}，委托数量：{}'.format(context.now, context.symbol, bar.close, 1))
                context.position = 1
                context.cancel_price = cancel_price
            elif ret > 0:
                # 开空仓
                order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell, position_effect=PositionEffect_Open,
                             order_type=OrderType_Market)
                context.log_func('{}:标的：{}，操作：开空仓，委托价格：{}，委托数量：{}'.format(context.now, context.symbol, bar.close, 1))
                context.position = -1
                context.cancel_price = cancel_price

        # 如果已经有多仓
        elif context.position > 0:
            op_flag = False  # 是否平仓标志
            if bar.close < context.cancel_price:
                # 破止损价平仓
                op_flag = True
            else:
                # 平仓点平仓
                ret = context.manager.is_sell_point()
                if ret > 0:
                    op_flag = True

            if op_flag:
                # 以市价平多仓
                order_volume(symbol=context.symbol, volume=1, side=OrderSide_Sell, position_effect=PositionEffect_Close,
                             order_type=OrderType_Market)
                context.log_func('{}:标的：{}，操作：平多仓，委托价格：{}，委托数量：{}'.format(context.now, context.symbol, bar.close, 1))
                context.position = 0

        # 如果已经有空仓
        elif context.position < 0:
            op_flag = False  # 是否平仓标志
            if bar.close > context.cancel_price:
                # 破止损价平仓
                op_flag = True
            else:
                # 平仓点平仓
                ret = context.manager.is_sell_point()
                if ret < 0:
                    op_flag = True

            if op_flag:
                # 以市价平空仓
                order_volume(symbol=context.symbol, volume=1, side=OrderSide_Buy,
                             position_effect=PositionEffect_Close, order_type=OrderType_Market)
                context.log_func('{}:标的：{}，操作：平空仓，委托价格：{}，委托数量：{}'.format(context.now, context.symbol, bar.close, 1))
                context.position = 0


def on_backtest_finished(context, indicator):
    context.log_func('*' * 50)
    context.log_func('回测已完成，请通过右上角“回测历史”功能查询详情。')


""" 以下为界面代码 """
class GmWidget(QWidget):
    signal_log: pyqtSignal = pyqtSignal(str)

    def __init__(self):
        super().__init__()

        self.signal_log.connect(self.process_log_event)

        # 执行线程
        self._thread: Thread = Thread(target=self._run)

        self.initUI()

    def initUI(self):
        opButton = QPushButton("执行策略")          # ①
        opButton.clicked.connect(self.onProcess)

        hbox = QHBoxLayout()                    # ②
        hbox.addStretch(4)
        hbox.addWidget(opButton)
        hbox.addStretch(1)

        self.textEdit = QTextEdit()

        vbox = QVBoxLayout()                    # ③
        vbox.addWidget(self.textEdit)
        vbox.addLayout(hbox)

        self.setLayout(vbox)                    # ④

        self.setGeometry(300, 300, 800, 500)
        self.setWindowTitle('盒布局')
        self.show()

    def process_log_event(self, mess: str) -> None:
        """"""
        self.textEdit.append(mess)

    def _run(self):
        """执行线程的主函数"""
        # 取得context实例
        from gm.model.storage import context
        # 设置显示回调函数
        context.log_func: Callable = self.signal_log.emit

        # 启动策略
        run(strategy_id='0ef986db-960a-11ed-b286-54ee75a30838',
            filename='widget.py',
            mode=MODE_BACKTEST,
            token='f01adc1f8a69fbc48d5675edb0a99b7486359bfd',
            backtest_start_time='2022-10-26 09:00:00',
            backtest_end_time='2023-01-18 15:00:00',
            backtest_adjust=ADJUST_PREV,
            backtest_initial_cash=1000000,
            backtest_commission_ratio=0.0001,
            backtest_slippage_ratio=0.0001)

    def onProcess(self):
        """执行策略按钮的槽函数"""
        self.textEdit.clear()
        self._thread.start()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = GmWidget()
    sys.exit(app.exec_())
